/*-------------------------------------------------------------------------
 *
 * planmain.c
 *	  计划单个查询的例程
 *
 * 名字中有什么呢？规划者/优化器的顶层入口点在 planner.c 中，而不是这里，虽然你可能会认为根据文件名会在这里。
 * 但这实际上是计划基本联接操作的主要代码，去掉了子查询、继承、聚合、分组等特性。
 * （这些都是 planner.c 处理的内容。）
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/plan/planmain.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "optimizer/appendinfo.h"
#include "optimizer/clauses.h"
#include "optimizer/inherit.h"
#include "optimizer/optimizer.h"
#include "optimizer/orclauses.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"


/*
 * query_planner
 *	  为基本查询生成路径（即简化计划），
 *	  这可能涉及联接但没有任何更复杂的功能。
 *
 * 由于 query_planner 不处理顶层处理（分组，
 * 排序等），因此它不能自行选择最佳路径。相反，它
 * 返回顶层联接的 RelOptInfo，调用者
 * （grouping_planner）可以在生存路径中进行选择。
 *
 * root 描述要规划的查询
 * qp_callback 是一个函数，用于在安全时计算 query_pathkeys
 * qp_extra 是可选的额外数据，传递给 qp_callback
 *
 * 注意：PlannerInfo 节点还包括一个 query_pathkeys 字段，
 * 它告知 query_planner 最终输出计划中所需的排序顺序。
 * 此值在调用时 *不可* 用，但在我们完成合并查询的等价类后由
 * qp_callback 计算。 （我们不能在完成之前构建规范路径键。）
 */
RelOptInfo * query_planner(PlannerInfo *fc_root,
			  query_pathkeys_callback fc_qp_callback, void *fc_qp_extra)
{
	Query	   *fc_parse = fc_root->parse;
	List	   *fc_joinlist;
	RelOptInfo *fc_final_rel;

	/*
	 * 初始化规划器列表为空。
	 *
	 * 注意：append_rel_list 是由 subquery_planner 设置的，因此请勿在此处更改。
	 */
	fc_root->join_rel_list = NIL;
	fc_root->join_rel_hash = NULL;
	fc_root->join_rel_level = NULL;
	fc_root->join_cur_level = 0;
	fc_root->canon_pathkeys = NIL;
	fc_root->left_join_clauses = NIL;
	fc_root->right_join_clauses = NIL;
	fc_root->full_join_clauses = NIL;
	fc_root->join_info_list = NIL;
	fc_root->placeholder_list = NIL;
	fc_root->fkey_list = NIL;
	fc_root->initial_rels = NIL;

	/*
	 * 设置用于访问基本关系和 AppendRelInfos 的数组。
	 */
	setup_simple_rel_arrays(fc_root);

	/*
	 * 在简单情况下，如果联合树是单个 RTE_RESULT 关系， 
	 * 则绕过该函数的其余部分，直接创建一个 RelOptInfo 及其 
	 * 唯一的访问路径。这值得优化，因为它适用于 
	 * 常见情况，例如 "SELECT expression" 和 "INSERT ... VALUES()"。
	 */
	Assert(fc_parse->jointree->fromlist != NIL);
	if (list_length(fc_parse->jointree->fromlist) == 1)
	{
		Node	   *fc_jtnode = (Node *) linitial(fc_parse->jointree->fromlist);

		if (IsA(fc_jtnode, RangeTblRef))
		{
			int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;
			RangeTblEntry *fc_rte = fc_root->simple_rte_array[fc_varno];

			Assert(fc_rte != NULL);
			if (fc_rte->rtekind == RTE_RESULT)
			{
				/* 直接为其创建 RelOptInfo */
				fc_final_rel = build_simple_rel(fc_root, fc_varno, NULL);

				/*
				 * 如果查询一般允许并行性，检查 quals 是否受限于并行。 
				 * （我们不需要检查 final_rel->reltarget，因为此时它是空的。 
				 * 查询 tlist 中的任何并行限制将在以后处理。） 
				 * 这通常是非常愚蠢的，因为仅 Result 计划永远不会有趣 
				 * 进行并行化。然而，如果 force_parallel_mode 打开， 
				 * 那么如果可能的话，我们希望在并行工作程序中执行 Result， 
				 * 因此我们必须这样做。
				 */
				if (fc_root->glob->parallelModeOK &&
					force_parallel_mode != FORCE_PARALLEL_OFF)
					fc_final_rel->consider_parallel =
						is_parallel_safe(fc_root, fc_parse->jointree->quals);

				/*
				 * 它唯一的路径是一个简单的 Result 路径。我们在这里稍微 
				 * 欺骗一下，使用 GroupResultPath，因为那样我们 
				 * 可以直接将 quals 塞进去而无需预处理。 
				 * （但是，如果你把头摆在正确的角度， 
				 * 没有 FROM 的 SELECT 是一种退化分组情况，所以这并不是 
				 * 作弊那么多。）
				 */
				add_path(fc_final_rel, (Path *)
						 create_group_result_path(fc_root, fc_final_rel,
												  fc_final_rel->reltarget,
												  (List *) fc_parse->jointree->quals));

				/* 选择最便宜的路径（在这种情况下相当简单...） */
				set_cheapest(fc_final_rel);

				/*  
                 * 我们不需要运行 generate_base_implied_equalities，但  
                 * 我们确实需要假装 EC 合并已完成。  
                 */  
				fc_root->ec_merging_done = true;

				/*  
                 * 我们仍然需要调用 qp_callback，以防它是  
                 * 类似于 "SELECT 2+2 ORDER BY 1" 的东西。  
                 */  
				(*fc_qp_callback) (fc_root, fc_qp_extra);

				return fc_final_rel;
			}
		}
	}

	/*  
	 * 为查询中使用的所有基本关系构造 RelOptInfo 节点。  
	 * Appendrel 成员关系（“其他关系”）将在稍后添加。  
	 *  
	 * 注意：我们通过搜索联合树查找基关系，而不是扫描  
	 * rangetable 的原因是，rangetable 可能包含非主动参与查询的  
	 * RTE，例如视图。我们不想为它们建立 RelOptInfos。  
	 */  
	add_base_rels_to_query(fc_root, (Node *) fc_parse->jointree);

	/*  
	 * 检查目标列表和连接树，为所有引用的变量  
	 * 添加条目到 baserel 目标列表，并生成  
	 * 所有引用的 PlaceHolderVars 的 PlaceHolderInfo 条目。  
	 * 限制和连接子句被添加到提到的关系所属的适当列表中。我们  
	 * 还为可证明等价的表达式构建等价类。  
	 * 还构建了 SpecialJoinInfo 列表，以保存关于连接顺序  
	 * 限制的信息。最后，我们形成一个目标连接列表供  
	 * make_one_rel() 使用。  
	 */  
	build_base_rel_tlists(fc_root, fc_root->processed_tlist);

	find_placeholders_in_jointree(fc_root);

	find_lateral_references(fc_root);

	fc_joinlist = deconstruct_jointree(fc_root);

	/*  
	 * 重新考虑任何延迟的外连接条件，现在我们建立了  
	 * 等价类。（这可能导致类的进一步添加或合并。）  
	 */  
	reconsider_outer_join_clauses(fc_root);

	/*  
	 * 如果我们形成了任何等价类，则根据需要生成额外的  
	 * 限制子句。（隐含连接子句将在稍后即时形成。）  
	 */  
	generate_base_implied_equalities(fc_root);

	/*  
	 * 我们已经完成了等价集合的合并，  
	 * 因此现在可以生成标准形式的 pathkeys；  
	 * 因此计算 PlannerInfo 中的 query_pathkeys  
	 * 和其他 pathkeys 字段。  
	 */  
	(*fc_qp_callback) (fc_root, fc_qp_extra);

	/*  
	 * 检查在子查询提升过程中生成的任何“占位符”表达式。  
	 * 确保他们需要的变量在相关连接级别被标记为需要。  
	 * 这必须在移除连接之前完成，因为这可能  
	 * 导致在连接上方需要变量或占位符，而之前未  
	 * 被标记为需要。  
	 */  
	fix_placeholder_input_needed_levels(fc_root);

	/*  
	 * 删除任何无用的外连接。理想情况下，这将在  
	 * 联合树预处理期间完成，但必要的信息在我们  
	 * 构建 baserel 数据结构和分类 qual 子句之前不可用。  
	 */  
	fc_joinlist = remove_useless_joins(fc_root, fc_joinlist);

	/*  
	 * 此外，将任何与唯一内部关系的半连接简化为普通内连接。  
	 * 同样，由于缺乏所需的信息，这不能在现在之前完成。  
	 */  
	reduce_unique_semijoins(fc_root);

	/*  
	 * 现在根据需要将“占位符”分配给基本关系。必须在  
	 * 移除连接之后完成，因为移除可能改变占位符是否在  
	 * 基本关系中可评估。  
	 */  
	add_placeholders_to_base_rels(fc_root);

	/*  
	 * 现在构建外部引用集，因为我们已经确认了  
	 * PlaceHolderVar 的评估级别。  
	 */  
	create_lateral_join_info(fc_root);

	/*  
	 * 将外键匹配到等价类和连接条件。  
	 * 这必须在最终确定等价类后完成，  
	 * 并且最好在移除连接后等到处理外键  
	 * 涉及移除的关系。  
	 */  
	match_foreign_keys_to_quals(fc_root);

	/*  
	 * 查找连接 OR 子句，从中我们可以提取单关系  
	 * 限制 OR 子句。  
	 */  
	extract_restriction_or_clauses(fc_root);

	/*  
	 * 现在通过为其子节点添加“otherrels”来扩展 appendrels。  
	 * 我们将这延迟到最后，以便为每个基本关系  
	 * 尽可能获取更多信息，包括所有限制子句。  
	 * 这让我们能够剪除不满足限制子句的分区。  
	 * 还注意到，一些信息，例如 lateral_relids，  
	 * 从 baserels 传播到 otherrels，因此我们必须  
	 * 已经计算出它。  
	 */  
	add_other_rels_to_query(fc_root);

	/*  
	 * 将任何 UPDATE/DELETE/MERGE 行标识变量分配给目标  
	 * 关系。在完成扩展 appendrels 之前，无法进行此操作。  
	 */  
	distribute_row_identity_vars(fc_root);

	/*  
	 * 准备进行主要规划。  
	 */  
	fc_final_rel = make_one_rel(fc_root, fc_joinlist);

	/* 检查我们是否至少有一个可用路径 */  
	if (!fc_final_rel || !fc_final_rel->cheapest_total_path ||
		fc_final_rel->cheapest_total_path->param_info != NULL)
		elog(ERROR, "failed to construct the join relation");

	return fc_final_rel;
}
